package net.cyl.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Assert;
import lombok.extern.slf4j.Slf4j;
import net.cyl.component.ShortLinkComponent;
import net.cyl.config.RabbitMQConfig;
import net.cyl.constant.RedisKey;
import net.cyl.controller.request.*;
import net.cyl.emums.BizCodeEnum;
import net.cyl.emums.DomainTypeEnum;
import net.cyl.emums.EventMessageType;
import net.cyl.emums.ShortLinkStateEnum;
import net.cyl.feign.TrafficFeignService;
import net.cyl.interceptor.LoginInterceptor;
import net.cyl.manager.GroupCodeMappingManager;
import net.cyl.manager.DomainManager;
import net.cyl.manager.LinkGroupManager;
import net.cyl.manager.ShortLinkManager;
import net.cyl.model.*;
import net.cyl.mapper.ShortLinkMapper;
import net.cyl.service.ShortLinkService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.cyl.util.CommonUtil;
import net.cyl.util.IDUtil;
import net.cyl.util.JsonData;
import net.cyl.util.JsonUtil;
import net.cyl.vo.ShortLinkVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 曹玉林
 * @since 2025-05-14
 */
@Service
@Slf4j
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements ShortLinkService {
    @Autowired
    private ShortLinkManager shortLinkManager;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RabbitMQConfig rabbitMQConfig;
    @Autowired
    private DomainManager domainManager;
    @Autowired
    private LinkGroupManager linkGroupManager;
    @Autowired
    private ShortLinkComponent shortLinkComponent;
    @Autowired
    private GroupCodeMappingManager groupCodeMappingManager;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private TrafficFeignService  trafficFeignService;
    @Override
    public ShortLinkVO parseShortLinkByCode(String shortLinkCode) {
        ShortLinkDO shortLinkDO = shortLinkManager.findByShortLinkCode(shortLinkCode);
        if (shortLinkDO == null) {
            return null;
        }
        ShortLinkVO shortLinkVO = new ShortLinkVO();
        BeanUtils.copyProperties(shortLinkDO, shortLinkVO);
        return shortLinkVO;
    }

    @Override
    public JsonData createShortLink(ShortLinkAddRequest request) {
        Long accountNo = LoginInterceptor.loginUserThreadLocal.get().getAccountNo();


        //需要预先检查下是否有足够多的可以进行创建
        String cacheKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);

        // 检查key是否存在，然后递减，是否大于等于0，使用lua脚本
        // 如果key不存在，则未使用过，lua返回值是0； 新增流量包的时候，不用重新计算次数，直接删除key,消费的时候回计算更新

        String script = "if redis.call('get',KEYS[1]) then return redis.call('decr',KEYS[1]) else return 0 end";

        Long leftTimes = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class),Arrays.asList(cacheKey),"");

        log.info("今日流量包剩余次数:{}",leftTimes);

        if(leftTimes>=0){
            String newOriginalUrl = CommonUtil.addUrlPrefix(request.getOriginalUrl());
            request.setOriginalUrl(newOriginalUrl);

            EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                    .messageBody(JsonUtil.obj2Json(request))
                    .messageId(IDUtil.generateSnowflakeKey().toString())
                    .eventType(EventMessageType.SHORT_LINK_ADD.name())
                    .build();

            rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchangeName(), rabbitMQConfig.getShortLinkAddRoutingKey(), eventMessage);

            return JsonData.buildSuccess();
        }else {
            //流量包不足
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
    }
    /**
     * 处理添加短链事件
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handleAddShortLink(EventMessage eventMessage) {

        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventType();

        ShortLinkAddRequest addRequest = JsonUtil.json2Obj(eventMessage.getMessageBody(), ShortLinkAddRequest.class);
        //短链域名校验
        DomainDO domainDO = checkDomain(addRequest.getDomainType(), addRequest.getDomainId(), accountNo);
        //校验组是否合法
        LinkGroupDO linkGroupDO = checkLinkGroup(addRequest.getGroupId(), accountNo);

        //长链摘要
        String originalUrlDigest = CommonUtil.MD5(addRequest.getOriginalUrl());

        //短链码重复标记
        boolean duplicateCodeFlag = false;

        //生成短链码
        String shortLinkCode = shortLinkComponent.createShortLinkCode(addRequest.getOriginalUrl());

        //加锁
        //key1是短链码，ARGV[1]是accountNo,ARGV[2]是过期时间
        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";

        Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(shortLinkCode), accountNo, 100);


        //加锁成功
        if (result > 0) {

            //C端处理
            if (EventMessageType.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(messageType)) {


                //先判断是否短链码被占用
                ShortLinkDO shortLinCodeDOInDB = shortLinkManager.findByShortLinkCode(shortLinkCode);

                if (shortLinCodeDOInDB == null) {
                    boolean reduceFlag = reduceTraffic(eventMessage,shortLinkCode);
                    if(reduceFlag){
                        ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                                .accountNo(accountNo).code(shortLinkCode)
                                .title(addRequest.getTitle()).originalUrl(addRequest.getOriginalUrl())
                                .domain(domainDO.getValue()).groupId(linkGroupDO.getId())
                                .expired(addRequest.getExpired()).sign(originalUrlDigest)
                                .state(ShortLinkStateEnum.AVAILABLE.name()).del(0).build();
                        shortLinkManager.addShortLink(shortLinkDO);
                        return true;
                    }
                } else {

                    duplicateCodeFlag = true;
                }


            } else if (EventMessageType.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(messageType)) {
                //B端处理
                GroupCodeMappingDO groupCodeMappingDOInDB = groupCodeMappingManager.findByCodeAndGroupId(shortLinkCode, linkGroupDO.getId(), accountNo);

                if (groupCodeMappingDOInDB == null) {

                    GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                            .accountNo(accountNo).code(shortLinkCode).title(addRequest.getTitle())
                            .originalUrl(addRequest.getOriginalUrl())
                            .domain(domainDO.getValue()).groupId(linkGroupDO.getId())
                            .expired(addRequest.getExpired()).sign(originalUrlDigest)
                            .state(ShortLinkStateEnum.AVAILABLE.name()).del(0).build();

                    groupCodeMappingManager.add(groupCodeMappingDO);
                    return true;

                } else {
                    System.out.println( "**********************/" +
                            "************************" +
                            "*************************" +
                            "**************************");
                    System.out.println( "短链码重复:{}");
                    duplicateCodeFlag = true;
                }

            }

        } else {

            //加锁失败，自旋100毫秒，再调用； 失败的可能是短链码已经被占用，需要重新生成
            log.error("加锁失败:{}", eventMessage);

            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
            }

            duplicateCodeFlag = true;

        }

        if (duplicateCodeFlag) {
            String newOriginalUrl = CommonUtil.addUrlPrefixVersion(addRequest.getOriginalUrl());
            addRequest.setOriginalUrl(newOriginalUrl);
            eventMessage.setMessageBody(JsonUtil.obj2Json(addRequest));
            log.warn("短链码报错失败，重新生成:{}", eventMessage);
            handleAddShortLink(eventMessage);
        }
        return false;
    }

    private boolean reduceTraffic(EventMessage eventMessage, String shortLinkCode) {
        UseTrafficRequest request = UseTrafficRequest.builder()
                .accountNo(eventMessage.getAccountNo()).bizId(shortLinkCode).build();

        JsonData jsonData = trafficFeignService.useTraffic(request);

        if(jsonData.getCode() != 0){
            log.error("流量包不足，扣减失败:{}",eventMessage);
            return false;
        }
        return true;
    }

    @Override
    public boolean handleDelShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventType();
        ShortLinkDelRequest delRequest = JsonUtil.json2Obj(eventMessage.getMessageBody(), ShortLinkDelRequest.class);
        if (EventMessageType.SHORT_LINK_DELETE_LINK.name().equalsIgnoreCase(messageType)) {
                ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                        .code(delRequest.getCode()).accountNo(accountNo).build();
                int rows = shortLinkManager.del(shortLinkDO);
                log.debug("删除短链成功:{}", rows);
                return true;
        }
        else if (EventMessageType.SHORT_LINK_DELETE_MAPPING.name().equalsIgnoreCase(messageType)) {
                GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                        .id(delRequest.getMappingId())
                        .accountNo(accountNo)
                        .groupId(delRequest.getGroupId()).build();

                int rows = groupCodeMappingManager.del(groupCodeMappingDO);
                log.debug("删除短链成功:{}", rows);
                return true;
        }
        return false;
    }

    @Override
    public boolean handleUpdateShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventType();
        ShortLinkUpdateRequest updateRequest = JsonUtil.json2Obj(eventMessage.getMessageBody(), ShortLinkUpdateRequest.class);
        DomainDO domainDO = null;

        if (updateRequest != null) {
            domainDO = checkDomain(updateRequest.getDomainType(), updateRequest.getDomainId(), accountNo);
        }

        if(EventMessageType.SHORT_LINK_UPDATE_LINK.name().equalsIgnoreCase(messageType)) {
            if (domainDO != null) {
                  ShortLinkDO shortLinkDO = ShortLinkDO.builder().title(updateRequest.getTitle())
                        .code(updateRequest.getCode()).title(updateRequest.getTitle())
                        .domain(domainDO.getValue())
                          .accountNo(accountNo).build();
                int rows=  shortLinkManager.update(shortLinkDO);
                log.debug("更新短链成功:{}", rows);
                return true;
            }
        }
        else if(EventMessageType.SHORT_LINK_UPDATE_MAPPING.name().equalsIgnoreCase(messageType)) {
                  GroupCodeMappingDO groupCodeMappingDO =  GroupCodeMappingDO.builder()
                          .id(updateRequest.getMappingId())
                          .groupId(updateRequest.getGroupId())
                          .code(updateRequest.getCode())
                          .title(updateRequest.getTitle())
                          .domain(domainDO.getValue())
                          .accountNo(accountNo).build();
                int rows=   groupCodeMappingManager.update(groupCodeMappingDO);
                log.debug("更新短链成功:{}", rows);
                return true;

        }
        return false;
    }

    @Override
    public Map<String, Object> pageByGroupId(ShortLinkPageRequest request) {
        Long accountNo = LoginInterceptor.loginUserThreadLocal.get().getAccountNo();
        return groupCodeMappingManager.pageShortLinkByGroupId(request.getGroupId(), accountNo, request.getPage(), request.getSize());
    }

    @Override
    public JsonData deleteShortLink(ShortLinkDelRequest request) {
        Long accountNo = LoginInterceptor.loginUserThreadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .messageBody(JsonUtil.obj2Json(request))
                .messageId(IDUtil.generateSnowflakeKey().toString())
                .eventType(EventMessageType.SHORT_LINK_DELETE.name())
                .build();
        //发送事件
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchangeName(), rabbitMQConfig.getShortLinkDeleteRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }

    @Override
    public JsonData updateShortLink(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.loginUserThreadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .messageBody(JsonUtil.obj2Json(request))
                .messageId(IDUtil.generateSnowflakeKey().toString())
                .eventType(EventMessageType.SHORT_LINK_UPDATE.name())
                .build();
        //发送事件
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchangeName(), rabbitMQConfig.getShortLinkUpdateRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }


    private ShortLinkDO buildShortLinkDO(Long accountNo, String shortLinkCode, DomainDO domainDO, LinkGroupDO linkGroupDO,
                                     Date expired, String originalUrl, String sign, String title) {
    return ShortLinkDO.builder()
            .accountNo(accountNo)
            .code(shortLinkCode)
            .domain(domainDO.getValue())
            .expired(expired)
            .groupId(linkGroupDO.getId())
            .state(ShortLinkStateEnum.AVAILABLE.name())
            .originalUrl(originalUrl)
            .sign(sign)
            .title(title)
            .del(0)
            .build();
}

private GroupCodeMappingDO buildGroupCodeMappingDO(Long accountNo, String shortLinkCode, DomainDO domainDO, LinkGroupDO linkGroupDO,
                                                   Date expired, String originalUrl, String sign, String title) {
    return GroupCodeMappingDO.builder()
            .accountNo(accountNo)
            .code(shortLinkCode)
            .domain(domainDO.getValue())
            .expired(expired)
            .groupId(linkGroupDO.getId())
            .state(ShortLinkStateEnum.AVAILABLE.name())
            .originalUrl(originalUrl)
            .sign(sign)
            .title(title)
            .del(0)
            .build();
}


    private DomainDO  checkDomain(String domainType,Long domainId,Long accountNo) {
         DomainDO domainDO;
        if (DomainTypeEnum.OFFICIAL.name().equalsIgnoreCase(domainType)){
            domainDO = domainManager.findById(domainId, accountNo);
        }
        else {
            domainDO = domainManager.findByDomainTypeAndId(domainId, DomainTypeEnum.OFFICIAL);
        }
        Assert.notNull(domainDO, "短链域名不合法");
        return domainDO;
    }

    private LinkGroupDO checkLinkGroup(Long linkGroupId, Long accountNo) {
        LinkGroupDO linkGroupDO = linkGroupManager.detail(linkGroupId, accountNo);
        Assert.notNull(linkGroupDO, "短链分组不合法");
        return linkGroupDO;
    }
}
